package com.zego.zegowawaji_server.service.socket;

import android.os.DeadObjectException;
import android.os.RemoteException;
import android.text.TextUtils;

import com.zego.base.utils.NetworkUtils;
import com.zego.base.utils.ServerTime;
import com.zego.base.utils.TimerUtil;
import com.zego.zegowawaji_server.ZegoApplication;
import com.zego.zegowawaji_server.service.ICSCallback;
import com.zego.zegowawaji_server.service.NetData;
import com.zego.zegowawaji_server.service.socket.utils.IMUtils;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;

/**
 * 负责即时通讯的重连机制，同时提供对外的操作接口
 */
public class AutoConnectMgr implements KeepAliveSocket.SocketConnectionListener {
    private final int DISCONNECT_TYPE_CONNECT_FAILED_TO_SEVER = 1;  // 连接服务器失败
    private final int DISCONNECT_TYPE_SEND_PACKET_TIMEOUT = 2;      // 发送数据超时
    private final int DISCONNECT_TYPE_SEVER_DISCONNECTED = 3;       // 服务器主动断开连接

    // 线程池
    private final Executor connectionExecutor = Executors.newSingleThreadExecutor(); // 连接线程池
    private final Executor sendExecutor = Executors.newCachedThreadPool();  // 发送消息线程池
    private Disposable disposable;
    private KeepAliveSocket socket = null;
    private ICSCallback iCSCallback = null;
    private volatile long uid = 999;//用户ID
    private String token;//用户token
    /**
     * 重连递增时间，每次2秒
     */
    private int incrementTime = 0;
    private boolean isFirstTry = true;
    private boolean heartBeating = false;   //是否需要发送心跳：长连接断线状态为false
    private int heartbeatSend = 0;          //本地发送心跳次数，当数值为2，再次发送心跳的时候重置为0
    private int heartbeatResend = 0;        //心跳回送次数

    private AutoConnectMgr() {
        socket = new KeepAliveSocket(TCPConstant.HOST, TCPConstant.PORT);
        socket.setSocketStateListener(this);
    }

    public static AutoConnectMgr getInstance() {
        return SingletonHolder.instance;
    }

    /**
     * 设置回调，和App进行交互。
     *
     * @param iCSCallback 回调实例。
     */
    public void setCallback(ICSCallback iCSCallback) {
        this.iCSCallback = iCSCallback;
    }

    // =================以下是关于内部自动连接维护的功能=================

    /**
     * 登录用的信息。
     * 自动重连时需要用到，需要保存到内存中。Service重启后，需要再次调用login。<br>
     * 如果token是null或者""，则将现有Socket断开。否则则重新连接。
     *
     * @param uid   用户uid。
     * @param token 登录用的cookie。
     */
    public void login(long uid, String token) {
        if (TextUtils.isEmpty(token)) {
            IMUtils.log("---socket login--->token is empty,login return.");
            logout();
            return;
        }
        this.uid = uid;
        this.token = token;

        if (heartBeating) return;//如果是保持心跳连接状态，就不再次登录
        TimerUtil.resetIncreaseTime();//主动重连接时重置间隔时间
        IMUtils.log("login: ------>发送连接服务器的消息");
        connect();
    }

    public KeepAliveSocket.SocketState getSocketStatus() {
        if (socket != null) {
            return socket.getSocketState();
        }
        return KeepAliveSocket.SocketState.INVALID;
    }

    /**
     * 退出登录，即断开即时通讯的连接。同时清除登录用的token。
     *
     * @see #login(long, String) login(long uid, String token)
     */
    public void logout() {
        IMUtils.log("logout: ------>与服务器断开连接");
        this.uid = 0;
        this.token = "";

        heartBeating = false;
        loopHeartbeatStatus();
        heartbeatSend = 0;
        heartbeatResend = 0;
        connectionExecutor.execute(new Runnable() {
            @Override
            public void run() {
                socket.disconnect(true);
            }
        });
        this.token = null;
    }

    public void send(final NetData packet) {
        sendExecutor.execute(new Runnable() {
            @Override
            public void run() {
                socket.sendPacket(packet);
            }
        });
    }

    /**
     * @return 请求发送的延时时长
     */
    private int getIncrementTime() {
        incrementTime = TimerUtil.increaseTime(TCPConstant.SOCKET_AUTO_CONNECT_Increment_Time, 60 * 1000, false);
        IMUtils.log("socket reconnect delayed time：" + incrementTime);
        return incrementTime;
    }

    /**
     * 以获取到的地址和秘钥登录及时通讯服务器
     */
    protected void connect() {
//        if (uid == 0) return;
        connectionExecutor.execute(new Runnable() {
            @Override
            public void run() {
                socket.connect();
            }
        });
        IMUtils.log("connect: ------>socket开始连接，hostIP：" + TCPConstant.HOST + ":" + TCPConstant.PORT);
    }

    /**
     * 重连，每次重连进行延时，防止刷服务器
     */
    protected void reConnect() {
//        if (uid == 0) return;
        try {
            if (disposable != null && !disposable.isDisposed()) {
                return;
            }
            Observable<Long> timer = Observable.timer(getIncrementTimeForReDomainName(), TimeUnit.MILLISECONDS);
            disposable = timer.subscribe(new Consumer<Long>() {
                @Override
                public void accept(Long aLong) throws Exception {
                    disposable.dispose();
                    disposable = null;
                    connect();
                }
            });
        } catch (Exception e) {
        }
    }

    /**
     * socket第一次断开时不切换域名，第二次时有网的情况下切换域名
     */
    private int getIncrementTimeForReDomainName() {
        int time = getIncrementTime();
        //int time =10*1000;
        if (isFirstTry && NetworkUtils.isConnected(ZegoApplication.getContext())) {
            isFirstTry = false;
        } else {
            if (time > 5 * 1000 && ZegoApplication.getContext() != null && NetworkUtils.isConnected(ZegoApplication.getContext())) {
                changeDomainName();
            }
        }
        return time;
    }

    private void changeDomainName() {
        try {
            if (iCSCallback != null) {
                iCSCallback.changeDomainName();
            }
        } catch (DeadObjectException de) {//如果服务挂了，就缓存当前消息，并重启ChatService
//            CoreService.startChatService(App.context);
            IMUtils.log("---AutoConnectMgr--->DeadObjectException");
        } catch (RemoteException e) {
            IMUtils.log("---AutoConnectMgr--->RemoteException");
        }
    }

    /**
     * 发送心跳消息
     */
    public void heartbeat() {
        IMUtils.log("---heartbeat--->heartBeating：" +
                heartBeating + "，heartbeatSend：" + heartbeatSend + "，heartbeatResend：" +
                heartbeatResend + "，packet loss：" + (heartbeatSend - heartbeatResend));
        if (!heartBeating) return;

        if (socket == null) {
            heartBeating = false;
            loopHeartbeatStatus();
        } else {
            //回送次数与发送次数误差校准，允许1次丢包。如果两次心跳均未收到回送，就重连socket
            if (heartbeatSend - heartbeatResend > 1) {
                heartBeating = false;
                loopHeartbeatStatus();
                heartbeatSend = 0;
                heartbeatResend = 0;

                onStatusChange(TCPConstant.SOCKET_STATUS_Disconnect, "心跳回送失败，socket重新进行连接");
                socket.disconnect(false);
                connect();
            } else {
                socket.sendPacket(getRevertHeartbeat());//心跳时发送的是回送类型的心跳消息
            }
        }
    }

    /**
     * 获取心跳消息数据:  目前同登录数据
     */
    private NetData getHeartbeat() {
        return getLoginData();
    }

    /**
     * 获取心跳回送消息数据
     *
     * @return NetData socket发送消息
     */
    private NetData getRevertHeartbeat() {
        if (heartbeatSend == 2) {//重置心跳发送与回送次数统计
            heartbeatSend = 0;
            heartbeatResend = 0;
        }
        heartbeatSend++;//叠加心跳发送次数
        return getHeartbeat();
    }

    /**
     * 老孙：获取登录信息
     */
    private NetData getLoginData() {
        return new NetData();
    }

    /**
     * 应用退出登录
     *
     * @param reason 退出原因：1[异地登陆踢下线]，2[密码验证失败，用户不存在等]，3[账号被封消息]
     */
    private void accountInvalid(int reason) {
        accountInvalid(reason, null);
    }

    private void accountInvalid(int reason, String content) {
        try {
            if (iCSCallback != null) iCSCallback.accountInvalid(reason, content);
        } catch (RemoteException e) {
            IMUtils.logThrowable(e);
        }
    }

    /**
     * 回送心跳状态变更
     */
    private void loopHeartbeatStatus() {
        try {
            if (iCSCallback != null) iCSCallback.heartbeatStatus(heartBeating);
        } catch (RemoteException e) {
            IMUtils.logThrowable(e);
        }
    }

    public void onDisconnect(int type) {
        //打印日志
        String disconnectType = "";
        switch (type) {
            case DISCONNECT_TYPE_CONNECT_FAILED_TO_SEVER:
                disconnectType = "连接服务器失败";
                break;
            case DISCONNECT_TYPE_SEND_PACKET_TIMEOUT:
                disconnectType = "发送数据超时";
                break;
            case DISCONNECT_TYPE_SEVER_DISCONNECTED:
                disconnectType = "服务器主动关闭";
                break;
        }
        IMUtils.log("socket断开连接：" + disconnectType);

        //暂停心跳，开始断线重连
        heartBeating = false;
        loopHeartbeatStatus();
        heartbeatSend = 0;
        heartbeatResend = 0;

        onStatusChange(TCPConstant.SOCKET_STATUS_Disconnect, "socket断开服务器连接");
        reConnect();
    }

    /**
     * 将即时通讯中收到消息通过ICSCallback抛出。
     */
    private void onMessage(NetData data) {
        IMUtils.log("onMessage:---->msgId:" + data.getMessageId() + ",sender:" + data.getFromId() + ",content:" + data.getContent());
        try {
            if (iCSCallback != null) {
                iCSCallback.onMessage(data);
            }
        } catch (DeadObjectException de) {//如果服务挂了，就缓存当前消息，并重启ChatService
//            CoreService.startChatService(App.context);
            IMUtils.log("---AutoConnectMgr--->DeadObjectException");
        } catch (RemoteException e) {
            IMUtils.log("---AutoConnectMgr--->RemoteException");
        }
    }

    /**
     * 将即时通讯中的状态变化发送到App中。
     *
     * @param type 类型。
     * @param msg  消息内容。
     */
    private void onStatusChange(final int type, final String msg) {
        try {
            if (iCSCallback != null) {
                iCSCallback.onStatusChange(type, msg);
            }
        } catch (DeadObjectException de) {//如果服务挂了，就重启聊天service
//            CoreService.startChatService(App.context);
            IMUtils.log("---AutoConnectMgr--->DeadObjectException");
        } catch (RemoteException e) {
            IMUtils.log("---AutoConnectMgr--->RemoteException");
        }
    }

    @Override
    public void onSocketConnected() {
        onStatusChange(TCPConstant.SOCKET_STATUS_Connected, "socket连接服务器成功");
        TimerUtil.resetIncreaseTime();

//        if (uid == 0) {
//            socket.disconnect(true);
//            return;
//        }

        // socket连接成功后发送数据包
        socket.sendPacket(getLoginData()); // 发送登录数据包
    }

    @Override
    public void onSocketConnecting() {
        onStatusChange(TCPConstant.SOCKET_STATUS_ConnectIng, "socket正在连接");
    }

    @Override
    public void onSocketConnectError() {
        onDisconnect(DISCONNECT_TYPE_CONNECT_FAILED_TO_SEVER);
    }

    @Override
    public void onSendPacketError(KeepAliveSocket.SocketState state, NetData failedData) {
        IMUtils.log("onSendPacketError:---->SocketState:" + state.name() + ",sender:" + failedData.getFromId() + ",content:" + failedData.getContent());
        try {
            if (iCSCallback != null) {
                iCSCallback.onSendMsgError(failedData);
            }
        } catch (DeadObjectException de) {//如果服务挂了，就缓存当前消息，并重启ChatService
//            CoreService.startChatService(App.context);
            IMUtils.log("---AutoConnectMgr--->DeadObjectException");
        } catch (RemoteException e) {
            IMUtils.log("---AutoConnectMgr--->RemoteException");
        }
    }

    @Override
    public void onSocketDisconnectByError() {
        onDisconnect(DISCONNECT_TYPE_SEND_PACKET_TIMEOUT);
    }

    @Override
    public void onReceivePacket(NetData data) {
        String msgBody = data.getMsgBody();
        IMUtils.log("---socket消息体：" + msgBody);
        JSONObject contentObject = null;

        if (!TextUtils.isEmpty(msgBody)) {
            try {
                contentObject = new JSONObject(msgBody);
                if (contentObject.has("Tm"))
                    ServerTime.setServerTime(contentObject.optLong("Tm"));//保存服务器时间戳
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        switch (data.getMsgType()) {
            // socket注册登录成功
            case TCPConstant.MSG_ID_LOGIN_SUC:
            case TCPConstant.MSG_ID_LOGIN_REPET:
                TCPConstant.setServerId(data.getRid());
                heartBeating = true;
                loopHeartbeatStatus();
                incrementTime = 0;
                onStatusChange(TCPConstant.SOCKET_STATUS_Login_Success, "socket用户登录成功：" + msgBody);
                break;

            case TCPConstant.MSG_ID_HEART_BEAT:
                TCPConstant.setServerId(data.getRid());
                heartbeatResend++;
                break;

            default:
                IMUtils.log("socket登录失败，开始重新连接");
                reConnect();
                break;
        }

        onMessage(data);
    }

    @Override
    public void onSocketDisconnectNormally() {
    }

    private static class SingletonHolder {
        static AutoConnectMgr instance = new AutoConnectMgr();
    }
}
